package datatypes.kdTree;

import java.util.Arrays;

import datatypes.Vertex;

/**
 * A Range is a collection of minimum/maximum value pairs, one each for each axis.
 * 
 * @author nils
 *
 */
public class Range implements Cloneable {
	private double[][] range;
	
	/**
	 * Constructor for a new Range of zero-values; that is, the only value
	 * in this newly constructed Range is the origin of the coordinate system.
	 * See {@link #setMin(double, int)} and {@link #setMax(double, int)} for
	 * methods to change the range values.
	 * @param numberOfAxes
	 */
	public Range(int numberOfAxes) {
		this.range = new double[2][numberOfAxes];
	}
	
	/**
	 * Constructor for a three-dimensional Range with the given range values.
	 * @param minX
	 * @param maxX
	 * @param minY
	 * @param maxY
	 * @param minZ
	 * @param maxZ
	 */
	public Range(	double minX, double maxX,
					double minY, double maxY,
					double minZ, double maxZ) {
		this.range = new double[2][3];
		this.range[0][0] = minX;
		this.range[0][1] = minY;
		this.range[0][2] = minZ;
		this.range[1][0] = maxX;
		this.range[1][1] = maxY;
		this.range[1][2] = maxZ;
	}
	
	/**
	 * Constructor for a two-dimensional Range with the given range values.
	 * @param minX
	 * @param maxX
	 * @param minY
	 * @param maxY
	 */
	public Range(	double minX, double maxX,
					double minY, double maxY) {
		this.range = new double[2][2];
		this.range[0][0] = minX;
		this.range[0][1] = minY;
		this.range[1][0] = maxX;
		this.range[1][1] = maxY;
	}
	
	public int getNumberOfAxes() {
		return range[0].length;
	}
	
	/**
	 * Get-accessor to the minimum value at the given axis.
	 * @param axis
	 * @return
	 */
	public double getMin(int axis) {
		if (axis >= getNumberOfAxes()) {
			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
			return 0;
		}
		return range[0][axis];
	}
	
	/**
	 * Set-accessor to the minimum value at the given axis.
	 * @param axis
	 * @return
	 */
	public void setMin(double value, int axis) {
		if (axis >= getNumberOfAxes()) {
			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
			return;
		}
		range[0][axis] = value;
	}
	
	/**
	 * Get-accessor to the maximum value at the given axis.
	 * @param axis
	 * @return
	 */
	public double getMax(int axis) {
		if (axis >= getNumberOfAxes()) {
			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
			return 0;
		}
		return range[1][axis];
	}
	
	/**
	 * Set-accessor to the maximum value at the given axis.
	 * @param axis
	 * @return
	 */
	public void setMax(double value, int axis) {
		if (axis >= getNumberOfAxes()) {
			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
			return;
		}
		range[1][axis] = value;
	}
	
	/**
	 * Checks whether this range fully contains another range.
	 * Nota bene: If the dimension of the ranges does not match,
	 * the excess axes of the higher-dimensional range will be ignored. 
	 * 
	 * @param other
	 * @return
	 */
	public boolean contains(Range other) {
		int minNumberOfAxes = Math.min(this.getNumberOfAxes(), other.getNumberOfAxes());
		for (int i = 0; i < minNumberOfAxes; i++) {
			if (this.getMin(i) > other.getMin(i)) { return false; }
			if (this.getMax(i) < other.getMax(i)) { return false; }
		}
		return true;
	}
	
	/**
	 * Checks whether this range fully contains a vertex.
	 * Nota bene: If the dimension of the range and the vertex do not match,
	 * the excess axes of the higher-dimensional item will be ignored. 
	 * 
	 * @param vertex
	 * @return
	 */
	public boolean contains(Vertex vertex) {
		int minNumberOfAxes = Math.min(this.getNumberOfAxes(), vertex.getDimension());
		for (int i = 0; i < minNumberOfAxes; i++) {
			if (this.getMin(i) > vertex.getEntry(i)) { return false; }
			if (this.getMax(i) < vertex.getEntry(i)) { return false; }
		}
		return true;
	}
	
	
	/**
	 * Checks whether this range intersects another range.
	 * Nota bene: If the dimension of the ranges does not match,
	 * the excess axes of the higher-dimensional range will be ignored. 
	 * 
	 * @param other
	 * @return
	 */
	public boolean intersects(Range other) {
		int minNumberOfAxes = Math.min(this.getNumberOfAxes(), other.getNumberOfAxes());
		for (int i = 0; i < minNumberOfAxes; i++) {
			if (this.getMin(i) > other.getMax(i)) { return false; }
			if (this.getMax(i) < other.getMin(i)) { return false; }
		}
		return true;
	}
	
	/**
	 * Deep copy of this range.
	 */
	public Range clone() {
			Range clone = new Range(this.getNumberOfAxes()); //(Range) super.clone();
			clone.range[0] = this.range[0].clone();
			clone.range[1] = this.range[1].clone();
			return clone;
	}
	
	/**
	 * Returns true if all values in both ranges are equal AND both ranges have the same dimension.
	 * @param other
	 * @return
	 */
	public boolean equals(Range other) {
		return Arrays.equals(this.range[0], other.range[0])
			&& Arrays.equals(this.range[1], other.range[1]);
	}
	
	@Override
	public String toString() {
		String s =  "Axis | min, max \n";
		for (int i = 0; i < this.getNumberOfAxes(); i++) {
			s += i + "    | " + this.getMin(i) + ", " + this.getMax(i) + "\n";
		}
		return s;
	}
}
